﻿using System;
using System.Net.Sockets;
using System.Threading;
using Framework.Packets;

namespace Framework.Networking
{
    /// <summary>
    /// Stream abstraction.
    /// Must be inherited by any client connection to provide all needed posibilities.
    /// Also will be inherited by internal server-side needs — for cross app comunication.
    /// </summary>
    public abstract class ISocketStream : IStream, IDisposable // refactor to standart disposing.
    {

        #region Fields

        /// <summary>
        /// The stream we're communicating with.
        /// </summary>
        private Socket socketStream;

        /// <summary>
        /// Gets the stream we're communicating with.
        /// </summary>
        public Socket StreamSocket { get { return socketStream; } }

        /// <summary>
        /// The thread we're in.
        /// </summary>
        private Thread myThread;

        /// <summary>
        /// The buffer we're working on.
        /// </summary>
        private byte[] buffer;

        /// <summary>
        /// Gets the buffer we're working on.
        /// </summary>
        public byte[] Buffer { get { return buffer; } }

        /// <summary>
        /// Thread timer. 
        /// Each async socket thread has no time limit. So we have to manually close it.
        /// </summary>
        private ManualResetEvent timeoutTimer = new ManualResetEvent(false);

        /// <summary>
        /// Gets thread timer. 
        /// Each async socket thread has no time limit. So we have to manually close it.
        /// </summary>
        public ManualResetEvent TimeoutTimer { get { return timeoutTimer; } }

        #endregion

        #region Stream Reader and writer

        /// <summary>
        /// SocketStreamReader instance for this stream.
        /// </summary>
        private SocketStreamReader reader;

        /// <summary>
        /// Gets SocketStreamReader instance for this stream.
        /// </summary>
        public SocketStreamReader Reader { get { return reader; } }

        /// <summary>
        /// SocketStreamWriter instance for this stream.
        /// </summary>
        private SocketStreamWriter writer;

        /// <summary>
        /// Gets SocketStreamWriter instance for this stream.
        /// </summary>
        public SocketStreamWriter Writer { get { return writer; } } 

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_client"></param>
        public ISocketStream(Socket _client)
        {
            socketStream = _client;
            buffer = new byte[8 * 1024];
            myThread = Thread.CurrentThread;

            reader = new SocketStreamReader(this);
            writer = new SocketStreamWriter(this);
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void Start() // TODO: remake to abstract to inherit in childs
        {
            //ThreadManager.AddThread(this.myThread);

            //Send first packet
            //Send(new FirstPacket());

            try
            {

                IAsyncResult timeOutHandle = socketStream.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), socketStream);
                
                timeoutTimer.Reset();

                if (timeoutTimer.WaitOne(60000, false) == false) // waiting 60 secs
                {
                    //LogServise.Info("Client " + socketStream.RemoteEndPoint + " disconnected.");
                    Dispose();
                    //throw new Exception("Timeout reading from socket!");
                }
            }
            catch (Exception e)
            {
                //LogServise.Error("Network.Client.Start()", e);
                Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ar"></param>
        protected virtual void OnReceive(IAsyncResult ar) // TODO: remake to abstract to inherit in childs
        {
            Socket s = (Socket)ar.AsyncState;
            try
            {
                int rv = s.EndReceive(ar);
                if (rv > 0)
                {
                    timeoutTimer.Set();
                    byte[] data = new byte[rv];
                    //Copy data from buffer to new data array
                    Array.Copy(buffer, 0, data, 0, rv);
                    /*
                    //Spread out data
                    ushort p_len = BitConverter.ToUInt16(data, 0);
                    if (p_len == (data.Length - 6))
                    {
                        Handler.Handle(this, data);
                    }
                    else
                    {
                        int cn = data.Length;
                        int index = 0;

                        while (cn > 0)
                        {
                            ushort plen = BitConverter.ToUInt16(buffer, index);
                            byte[] buff = new byte[plen + 6];
                            Array.Copy(buffer, index, buff, 0, plen + 6);

                            Handler.Handle(this, buff);

                            index += (plen + 6);
                            cn -= (plen + 6);
                        }
                    }*/

                    // TODO: logging and reporting

                    //LogServise.Info(data.Length.ToString());
                    //LogServise.Info(Converter.ToString(data));

                    //Clear buffer array
                    Array.Clear(buffer, 0, buffer.Length);
                    //Receive more data
                    socketStream.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), socketStream);
                }
                else 
                {
                    // TODO: logging and reporting
                    //LogServise.Info("Client " + socketStream.RemoteEndPoint + " disconnected.");
                    Dispose();
                }
            }
            catch (Exception e)
            {
                if (e is SocketException)
                {
                    SocketException se = (SocketException)e;
                    //Client disconnected
                    if (se.ErrorCode == 10054)
                    {
                        // TODO: logging and reporting
                        //LogServise.Info("Client " + socketStream.RemoteEndPoint + " disconnected.");
                    }
                    else
                    {
                        // TODO: logging and reporting
                        //LogServise.Error("Network.Client.OnRecieve()", e);
                    }

                    Dispose();
                }
                // TODO: logging and reporting
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void Stop()
        {
            try
            {
                socketStream.Close(60); // secs as i remember
            }
            catch (Exception e)
            {
                // TODO: logging and reporting
                // TODO: it's a generic exception handler, we need to break them to Socket and others. 
                //LogServise.Error("Network.Server.OnAccept(IAsyncResult)", e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="packet"></param>
        public virtual void Send(IPacket packet)
        {
            if (packet == null)
            {
                throw new ArgumentNullException("packet", "You're trying to append null.");
            }
            // TODO: logging and reporting
            Send(packet.Data);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="packetWriter"></param>
        public void Send(PacketWriter packetWriter)
        {
            if (packetWriter == null)
            {
                throw new ArgumentNullException("packetWriter", "You're trying to append null.");
            }
            // TODO: logging and reporting
            Send(packetWriter.Packet.Data);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="packet"></param>
        public void Send(byte[] packet)
        {
            if (packet == null)
            {
                throw new ArgumentNullException("packet", "You're trying to append null.");
            }

            try
            {
                socketStream.BeginSend(packet, 0, packet.Length, SocketFlags.None, new AsyncCallback(EndSend), this.socketStream);
            }
            catch (Exception e)
            {
                // TODO: logging and reporting
                //LogServise.Error("Network.Client.Send()", e);
            }
            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ar"></param>
        protected virtual void EndSend(IAsyncResult ar) // TODO: remake to abstract to inherit in childs
        {
            Socket s = (Socket)ar.AsyncState;

            try
            {
                int sent = s.EndReceive(ar);
            }
            catch (Exception e)
            {
                // TODO: logging and reporting
                //LogServise.Error("Network.Client.EndSend()", e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void Dispose() 
        {
            //Close socket
            if (socketStream != null)
                socketStream.Close();

            //Close sql connection
            //this._sql.Disconnect();

            //Stop current thread
            if (myThread != null)
                myThread.Join();
        }
    }
}
